using PuppeteerSharp;

namespace WebAgent
{

    public class BrowserPool
    {
        private readonly Stack<IBrowser> _pool = new Stack<IBrowser>();
        static LaunchOptions launchOptions;
        static BrowserPool()
        {
            launchOptions = new LaunchOptions
            {
                Timeout = 1000 * 10,
                Args = new[] {
                    "--no-sandbox",
                    "--disable-gpu",
                    "--disable-dev-shm-usage",
                    "--disable-setuid-sandbox",
                    "--no-first-run",
                    "--no-zygote",
                    //"--single-process",
                    "--disable-seccomp-filter-sandbox"
                },
                Headless = true // 无头模式
            };

            //#if !DEBUG

            //            // launchOptions.ExecutablePath = "/home/ubuntu/webagent/WebAgent/wwwroot/Chrome/Linux-130.0.6723.69/chrome-linux64/chrome";
            //            launchOptions.ExecutablePath = "/app/wwwroot/Chrome/Linux-130.0.6723.69/chrome-linux64/chrome";
            //#endif

        }
        public int MaxFreeCount { get; set; } = 3;
        public async ValueTask<IBrowser> GetObjectAsync()
        {
            if (_pool.Count > 0)
            {
                return _pool.Pop();
            }
            return await Puppeteer.LaunchAsync(launchOptions);
        }
        public async ValueTask ReturnObjectAsync(IBrowser obj)
        {
            try
            {
                if (_pool.Count <= MaxFreeCount)
                {
                    _pool.Push(obj);
                    return;
                }

                await obj.CloseAsync();
                obj.Dispose();
            }
            catch (Exception _)
            {
                Console.WriteLine("ReturnObject Error is " + _.Message);
            }
        }
    }

    /// <summary>
    /// 代理，Vue渲染为完整HTML后将HTML返回，对SEO友好
    /// </summary>
    public class SSRMiddleware
    {
        private readonly RequestDelegate _next;
        static BrowserPool _browserPool;
        static SSRMiddleware()
        {
            _browserPool = new BrowserPool { MaxFreeCount = 10 };
        }
        public SSRMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            if (httpContext.Request.Headers.ContainsKey("ignore"))
            {
                var httpClient = httpContext.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient();
                var res = await httpClient.GetStringAsync(GetTargetUrl(httpContext), httpContext.RequestAborted);
                await httpContext.Response.WriteAsync(res, httpContext.RequestAborted);
                return;
            }

            var source = new CancellationTokenSource();
            httpContext.RequestAborted.Register(source.Cancel);
            var first = GetResponseAsync(httpContext, source.Token);
            try
            {
                await first.WaitAsync(TimeSpan.FromSeconds(20), httpContext.RequestAborted);
            }
            catch (Exception ex)
            {
                source.Cancel();
                Console.WriteLine("wait error is :{0}", ex.Message);
            }
            await httpContext.Response.WriteAsync(await first, httpContext.RequestAborted);
        }

        static string GetTargetUrl(HttpContext httpContext)
        {
            if (httpContext.Request.Headers.ContainsKey("url"))
                return httpContext.Request.Headers["url"][0]!;
            return httpContext.Request.QueryString.Value!.Substring(1);
        }

        static async Task<string> GetResponseAsync(HttpContext httpContext, CancellationToken cancellationToken)
        {
            var browser = await _browserPool.GetObjectAsync();
            try
            {
                await using (var page = await browser.NewPageAsync())
                {
                    cancellationToken.Register(async () =>
                    {
                        try
                        {
                            await page.CloseAsync();
                            await page.DisposeAsync();
                        }
                        catch
                        {
                        }
                    });
                    var navigationOptions = new NavigationOptions
                    {
                        WaitUntil = new[] { WaitUntilNavigation.Networkidle2 }, // 等待网络空闲（500ms内没有请求）
                        Timeout = 15000 // 超时时间15秒
                    };
                    var res = await page.GoToAsync(GetTargetUrl(httpContext), navigationOptions);
                    return await page.GetContentAsync();
                }
            }
            finally
            {
                await _browserPool.ReturnObjectAsync(browser);
            }
        }
    }

    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class MiddlewareExtensions
    {
        public static IApplicationBuilder UseSSRMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<SSRMiddleware>();
        }
    }
}
